"""Version management module for system updates.

Handles version information storage, retrieval, comparison,
and provides APIs for version queries.
"""

import json
import logging
import os
import tempfile
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Dict, Optional, List, Tuple
from datetime import datetime


logger = logging.getLogger(__name__)


class UpdateError(SystemError):
    """Custom exception for update-related errors."""
    pass


@dataclass
class Version:
    """Represents a semantic version with optional build number."""
    major: int
    minor: int
    patch: int
    build: Optional[str] = None
    
    @classmethod
    def from_string(cls, version_str: str) -> 'Version':
        """Parse a version string into Version object.
        
        Args:
            version_str: Version string like "1.2.0" or "1.2.0-20250806"
            
        Returns:
            Version object
            
        Raises:
            UpdateError: If version string format is invalid
        """
        try:
            # Handle build number if present
            if '-' in version_str:
                version_part, build = version_str.split('-', 1)
            else:
                version_part = version_str
                build = None
            
            # Parse major.minor.patch
            parts = version_part.split('.')
            if len(parts) != 3:
                raise ValueError(f"Invalid version format: {version_str}")
                
            major = int(parts[0])
            minor = int(parts[1])
            patch = int(parts[2])
            
            return cls(major=major, minor=minor, patch=patch, build=build)
            
        except (ValueError, IndexError) as e:
            raise UpdateError(f"无法解析版本号 '{version_str}': {str(e)}")
    
    def to_string(self) -> str:
        """Convert Version object to string format."""
        base = f"{self.major}.{self.minor}.{self.patch}"
        if self.build:
            return f"{base}-{self.build}"
        return base
    
    def __str__(self) -> str:
        return self.to_string()
    
    def __eq__(self, other: 'Version') -> bool:
        """Check if two versions are equal (ignoring build number)."""
        return (self.major == other.major and 
                self.minor == other.minor and 
                self.patch == other.patch)
    
    def __lt__(self, other: 'Version') -> bool:
        """Check if this version is less than another."""
        if self.major != other.major:
            return self.major < other.major
        if self.minor != other.minor:
            return self.minor < other.minor
        return self.patch < other.patch
    
    def __gt__(self, other: 'Version') -> bool:
        """Check if this version is greater than another."""
        return not (self < other or self == other)


@dataclass
class ComponentVersion:
    """Version information for a system component."""
    app: str
    ble: str
    system: str
    build: Optional[str] = None


@dataclass
class VersionInfo:
    """Complete version information including history."""
    current: ComponentVersion
    history: List[Dict] = None
    
    def __post_init__(self):
        if self.history is None:
            self.history = []


class VersionManager:
    """Manages system version information and updates."""
    
    def __init__(self, config_path: str = "config/version.json"):
        """Initialize the version manager.
        
        Args:
            config_path: Path to version configuration file
        """
        self.config_path = Path(config_path)
        self.version_info: Optional[VersionInfo] = None
        self._ensure_config_dir()
        self._load_version_info()
    
    def _ensure_config_dir(self):
        """Ensure the configuration directory exists."""
        self.config_path.parent.mkdir(parents=True, exist_ok=True)
    
    def _load_version_info(self):
        """Load version information from configuration file."""
        if self.config_path.exists():
            try:
                with open(self.config_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    
                # Convert dict to ComponentVersion
                current_data = data.get('current', {})
                current = ComponentVersion(
                    app=current_data.get('app', '1.0.0'),
                    ble=current_data.get('ble', '1.0.0'),
                    system=current_data.get('system', '1.0.0'),
                    build=current_data.get('build')
                )
                
                self.version_info = VersionInfo(
                    current=current,
                    history=data.get('history', [])
                )
                
                logger.info(f"Loaded version info: app={current.app}")
                
            except (json.JSONDecodeError, KeyError) as e:
                logger.error(f"Error loading version config: {e}")
                self._init_default_version()
        else:
            self._init_default_version()
    
    def _init_default_version(self):
        """Initialize with default version information."""
        self.version_info = VersionInfo(
            current=ComponentVersion(
                app="1.0.0",
                ble="1.0.0",
                system="1.0.0",
                build=datetime.now().strftime("%Y%m%d")
            )
        )
        self._save_version_info()
        logger.info("Initialized with default version info")
    
    def _save_version_info(self):
        """Save version information to file atomically."""
        if not self.version_info:
            raise UpdateError("No version information to save")
        
        # Prepare data for JSON serialization
        data = {
            'current': asdict(self.version_info.current),
            'history': self.version_info.history
        }
        
        # Write to temporary file first (atomic operation)
        try:
            temp_fd, temp_path = tempfile.mkstemp(
                dir=self.config_path.parent,
                suffix='.tmp'
            )
            
            with os.fdopen(temp_fd, 'w', encoding='utf-8') as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
            
            # Atomic rename
            os.replace(temp_path, self.config_path)
            logger.info("Version info saved successfully")
            
        except Exception as e:
            # Clean up temp file if exists
            if 'temp_path' in locals() and os.path.exists(temp_path):
                os.unlink(temp_path)
            raise UpdateError(f"Failed to save version info: {e}")
    
    def get_current_version(self) -> ComponentVersion:
        """Get current version information.
        
        Returns:
            Current component versions
        """
        if not self.version_info:
            self._load_version_info()
        return self.version_info.current
    
    def check_latest_version(self) -> Dict:
        """Check for latest available version (simulated).
        
        Returns:
            Dictionary with latest version information
        """
        # Simulated server check - in production would make HTTP request
        current = self.get_current_version()
        current_app = Version.from_string(current.app)
        
        # Simulate newer version available
        latest_app = Version(
            major=current_app.major,
            minor=current_app.minor + 1,
            patch=0
        )
        
        return {
            "app": latest_app.to_string(),
            "available": latest_app > current_app,
            "url": f"https://update.example.com/v{latest_app.to_string()}.tar.gz",
            "size": 15728640,  # 15MB
            "changelog": "Bug fixes and improvements"
        }
    
    def get_version_info(self) -> Dict:
        """Get complete version information in JSON format.
        
        Returns:
            Dictionary with current and latest version info
        """
        current = self.get_current_version()
        latest = self.check_latest_version()
        
        return {
            "current": asdict(current),
            "latest": latest,
            "update_channel": "stable",
            "last_check": datetime.now().isoformat()
        }
    
    def compare_versions(self, version1: str, version2: str) -> int:
        """Compare two version strings.
        
        Args:
            version1: First version string
            version2: Second version string
            
        Returns:
            -1 if version1 < version2
             0 if version1 == version2
             1 if version1 > version2
        """
        v1 = Version.from_string(version1)
        v2 = Version.from_string(version2)
        
        if v1 < v2:
            return -1
        elif v1 > v2:
            return 1
        else:
            return 0
    
    def is_compatible(self, required_version: str, current_version: str = None) -> bool:
        """Check if current version meets minimum requirements.
        
        Args:
            required_version: Minimum required version
            current_version: Version to check (uses system version if None)
            
        Returns:
            True if current version >= required version
        """
        if current_version is None:
            current_version = self.get_current_version().app
        
        return self.compare_versions(current_version, required_version) >= 0
    
    def update_version(self, component: str, new_version: str):
        """Update version for a specific component.
        
        Args:
            component: Component name ('app', 'ble', 'system')
            new_version: New version string
        """
        if component not in ['app', 'ble', 'system']:
            raise UpdateError(f"Unknown component: {component}")
        
        # Validate version format
        Version.from_string(new_version)
        
        # Record current version in history
        current = self.get_current_version()
        history_entry = {
            'timestamp': datetime.now().isoformat(),
            'component': component,
            'from_version': getattr(current, component),
            'to_version': new_version
        }
        
        # Update version
        setattr(self.version_info.current, component, new_version)
        
        # Add to history (keep last 10 entries)
        self.version_info.history.insert(0, history_entry)
        self.version_info.history = self.version_info.history[:10]
        
        # Save changes
        self._save_version_info()
        logger.info(f"Updated {component} version to {new_version}")
        # Console logging for non-BLE mode
        print(f"[VERSION-UPDATE] Updated {component}: {getattr(current, component)} -> {new_version}")
    
    def create_backup(self) -> str:
        """Create a backup of current version configuration.
        
        Returns:
            Path to backup file
        """
        if not self.config_path.exists():
            raise UpdateError("No version configuration to backup")
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_path = self.config_path.parent / f"version_backup_{timestamp}.json"
        
        with open(self.config_path, 'r') as src:
            with open(backup_path, 'w') as dst:
                dst.write(src.read())
        
        logger.info(f"Created backup at {backup_path}")
        return str(backup_path)
    
    def restore_backup(self, backup_path: str):
        """Restore version configuration from backup.
        
        Args:
            backup_path: Path to backup file
        """
        backup = Path(backup_path)
        if not backup.exists():
            raise UpdateError(f"Backup file not found: {backup_path}")
        
        # Validate backup file format
        try:
            with open(backup, 'r') as f:
                json.load(f)  # Just validate JSON
        except json.JSONDecodeError as e:
            raise UpdateError(f"Invalid backup file: {e}")
        
        # Restore the backup
        with open(backup, 'r') as src:
            with open(self.config_path, 'w') as dst:
                dst.write(src.read())
        
        # Reload version info
        self._load_version_info()
        logger.info(f"Restored version config from {backup_path}")